#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 设置具有执行功能的按钮为不可用状态
    setBtnDisabled();
    // 隐藏treeWidget表头
    ui->LRNodeTreeWidget->setHeaderHidden(true);
    ui->LALRNodeTreeWidget->setHeaderHidden(true);
    ui->treeWidget->setHeaderHidden(true);
    // 设置加载界面
    loadingDialog = new QProgressDialog(this);
    loadingDialog->setWindowModality(Qt::WindowModal);
    loadingDialog->setFixedSize(400, 100);
    loadingDialog->setCancelButtonText(nullptr);
    ; // 取消按钮
    loadingDialog->setWindowFlags((windowFlags() & ~Qt::WindowCloseButtonHint));
    loadingDialog->close(); // 不关闭的话启动程序时会莫名弹一个框
}

/**
 * @brief Widget::buildGTree
 * 构建语法树
 * @param node 当前节点
 * @param father 当前节点的父节点
 */
void Widget::buildGTree(GNode node, QTreeWidgetItem *father)
{
    auto thisNode = new QTreeWidgetItem(father);
    string nodeName = node.value.empty() ? node.name : node.value;
    if (nodeName == NULL_CHARACTER)
        nodeName = ""; // 不展示空
    thisNode->setText(0, QString::fromStdString(nodeName));
    // 子节点在下面，以自己为父节点
    for (auto child : node.children)
        buildGTree(child, thisNode);
}

/**
 * @brief Widget::openFile
 * 打开文件，返回文件路径，否则返回空
 * @param title 弹框标题
 * @param types 文件类型
 * @return 文件路径或空字符串
 */
QString Widget::openFile(QString title, QString types)
{
    // 创建一个QFileDialog对象
    QFileDialog dialog(this);

    // 设置对话框标题
    dialog.setWindowTitle(title);

    // 设置默认文件名过滤器
    dialog.setNameFilter(types);

    // 设置默认的起始目录
    dialog.setDirectory(QDir::current());

    // 打开文件选择对话框
    if (dialog.exec())
    {
        // 获取用户选择的文件路径
        QString selectedFile = dialog.selectedFiles().first();
        return selectedFile;
    }
    return "";
}

/**
 * @brief Widget::readFile
 * 读取文件内容，返回文件内容或空字符串
 * @param filePath 文件路径
 * @return 文件内容或空字符串
 */
QString Widget::readFile(QString filePath)
{
    QFile file(filePath);

    if (file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QTextStream in(&file);
        QString content = in.readAll();
        file.close();
        return content;
    }
    else
        return "";
}

/**
 * @brief Widget::saveToFile
 * 保存内容到本地文件
 * @param content 保存的内容
 * @param fileSuffix 文件名后缀
 */
void Widget::saveFile(const QString &content, const QString &fileSuffix)
{
    // 弹出保存文件对话框，让用户选择文件保存位置
    QString fileName = QFileDialog::getSaveFileName(this, "保存文件", "./", "文件 (*" + fileSuffix + ")");
    if (!fileName.isEmpty())
    {
        QFile file(fileName);
        if (!file.open(QFile::WriteOnly | QFile::Truncate))
        {
            // 如果文件无法打开，显示错误提示框
            QMessageBox::critical(this, "保存文件失败", "无法打开文件以写入数据。");
            return;
        }
        QTextStream out(&file);
        // 获取文本编辑器的内容并写入文件
        out << content;
        file.close();
        // 文件保存成功，显示成功提示框
        QMessageBox::information(this, "保存文件成功", "文件已成功保存到本地。");
    }
}

/**
 * @brief Widget::clearTab
 * 清空所有自动机标签的内容
 */
void Widget::clearTab()
{
    for (int i = ui->NFATab->count() - 1; i >= 0; --i)
        ui->NFATab->removeTab(i);
    for (int i = ui->DFATab->count() - 1; i >= 0; --i)
        ui->DFATab->removeTab(i);
    for (int i = ui->MinDFATab->count() - 1; i >= 0; --i)
        ui->MinDFATab->removeTab(i);
}

/**
 * @brief Widget::addTabWithTable
 * 在指定标签控件添加一个新的标签，标签名为参数中的tabName，在该新标签中添加一个表格控件展示自动机数据、两个文本框展示起点和终点集并设置好布局
 * @param tabWidget 需要添加新标签的标签控件
 * @param tabName 新标签名
 */
void Widget::addTabWithTableAndLineEdits(QTabWidget *tabWidget, const QString &tabName)
{
    // 创建一个新的QWidget，作为新的标签页
    QWidget *tabPage = new QWidget(tabWidget);

    // 创建一个QTableWidget，并设置一些基本属性
    QTableWidget *tableWidget = new QTableWidget(tabPage);
    tableWidget->setRowCount(0);    // 初始时没有行
    tableWidget->setColumnCount(0); // 初始时没有列
    // 起名字，用于后面获取该控件
    tableWidget->setObjectName(tabName + "::tableWidget");

    // 展示起点和终点集的控件
    // 使用QLabel而不是QLineEdit控件，看起来更舒服一点
    QLabel *startPointLabel = new QLabel(tabPage);
    // 起名字，用于后面获取该控件
    startPointLabel->setObjectName(tabName + "::startPointLabel");
    QLabel *endPointLabel = new QLabel(tabPage);
    endPointLabel->setObjectName(tabName + "::endPointLabel");

    // 创建布局并添加控件
    QVBoxLayout *layout = new QVBoxLayout(tabPage);
    layout->addWidget(tableWidget, 10); // 表格占大部分空间，权重为10
    layout->addWidget(startPointLabel);
    layout->addWidget(endPointLabel);

    // 设置布局为标签页的布局，使表格控件填满整个标签页
    tabPage->setLayout(layout);

    // 将标签页添加到QTabWidget中
    tabWidget->addTab(tabPage, tabName);
}

/**
 * @brief Widget::initTab
 * 根据当前保存的自动机数据来新增标签控件并初始化各标签中的数据
 * @param reName 当前正则名
 */
void Widget::initTab(const string &reName)
{
    addNFATab(reName);
    addDFATab(reName);
    addMinDFATab(reName);
}

/**
 * @brief Widget::addNFATab
 * 根据当前保存的NFA数据来填充NFA标签页面的数据，包括表格数据、起点和终点集数据
 * @param reName 正则名称
 */
void Widget::addNFATab(const string &reName)
{
    // 添加标签页面
    addTabWithTableAndLineEdits(ui->NFATab, QString::fromStdString(reName));
    // 找到最后一个标签页面就是新加的这个页面
    QWidget *tabPage = ui->NFATab->widget(ui->NFATab->count() - 1);

    // 找到表格控件
    QTableWidget *NFATable = qobject_cast<QTableWidget *>(tabPage->findChild<QTableWidget *>(QString::fromStdString(reName) + "::tableWidget"));
    // 构建表头，NFA还需要EPSILON
    symbols.insert(EPSILON);
    vector<string> headers;
    for (auto c : symbols)
    {
        if (c == "\n")
            headers.push_back("\\n");
        else
            headers.push_back(c);
    }
    // 构建数据
    vector<vector<string>> data;
    for (size_t i = 0; i < NFANodes.size(); i++)
    {
        data.emplace_back();
        // 下标0存本点的下标，NFA点的下标和在数组里的下标一致
        data[i].push_back(to_string(i));
        // 遍历所有字符，找到所有对应的出点
        string toNodes;
        for (auto c : symbols)
        {
            // 判断该点有没有边的值是c，有就存入边上对应的终点
            if (NFANodes[i].tableMap.count(c) && !NFANodes[i].tableMap[c].empty())
            {
                toNodes = to_string(NFANodes[i].tableMap[c][0]);
                for (size_t j = 1; j < NFANodes[i].tableMap[c].size(); j++)
                {
                    toNodes += ',';
                    toNodes += to_string(NFANodes[i].tableMap[c][j]);
                }
            }
            // 否则就为空
            else
            {
                toNodes = "";
            }
            // 插入数据
            data[i].push_back(toNodes);
        }
    }
    // 构建表格
    fillTableWithHeadersAndData(NFATable, headers, data);

    // 构建起点和终点数据
    // 这部分代码三种自动机都是一样的，使用提取成的方法即可
    fillStartAndEndPoints(tabPage, QString::fromStdString(reName), NFAStart, NFAEnds);
    symbols.erase(EPSILON);
}

/**
 * @brief Widget::addDFATab
 * 根据当前保存的DFA数据来填充DFA标签页面的数据，包括表格数据、起点和终点集数据
 * @param reName 正则名称
 */
void Widget::addDFATab(const string &reName)
{
    // 添加标签页面
    addTabWithTableAndLineEdits(ui->DFATab, QString::fromStdString(reName));
    // 找到最后一个标签页面就是新加的这个页面
    QWidget *tabPage = ui->DFATab->widget(ui->DFATab->count() - 1);

    // 找到表格控件
    QTableWidget *DFATable = qobject_cast<QTableWidget *>(tabPage->findChild<QTableWidget *>(QString::fromStdString(reName) + "::tableWidget"));
    // 构建表头
    vector<string> headers;
    for (auto c : symbols)
    {
        if (c == "\n")
            headers.push_back("\\n");
        else
            headers.push_back(c);
    }
    // 构建数据
    vector<vector<string>> data;
    for (auto node : DFANodes)
    {
        data.emplace_back();
        // 下标0存本点的下标
        data[data.size() - 1].push_back(to_string(node.first));
        // 遍历字符，找到所有出边
        for (auto c : symbols)
        {
            if (node.second.count(c))
                data[data.size() - 1].push_back(to_string(node.second[c]));
            else
                data[data.size() - 1].push_back("");
        }
    }
    // 构建表格
    fillTableWithHeadersAndData(DFATable, headers, data);

    // 构建起点和终点数据
    fillStartAndEndPoints(tabPage, QString::fromStdString(reName), DFAStart, DFAEnds);
}

/**
 * @brief Widget::addMinDFATab
 * 根据当前保存的MinDFA数据来填充MinDFA标签页面的数据，包括表格数据、起点和终点集数据
 * @param reName 正则名称
 */
void Widget::addMinDFATab(const string &reName)
{
    // 添加标签页面
    addTabWithTableAndLineEdits(ui->MinDFATab, QString::fromStdString(reName));
    // 找到最后一个标签页面就是新加的这个页面
    QWidget *tabPage = ui->MinDFATab->widget(ui->MinDFATab->count() - 1);

    // 找到表格控件
    QTableWidget *MinDFATable = qobject_cast<QTableWidget *>(tabPage->findChild<QTableWidget *>(QString::fromStdString(reName) + "::tableWidget"));
    // 构建表头
    vector<string> headers;
    for (auto c : symbols)
    {
        if (c == "\n")
            headers.push_back("\\n");
        else
            headers.push_back(c);
    }
    // 构建数据
    vector<vector<string>> data;
    for (auto node : MinDFANodes)
    {
        data.emplace_back();
        // 下标0存本点的下标
        data[data.size() - 1].push_back(to_string(node.first));
        // 遍历字符，找到所有出边
        for (auto c : symbols)
        {
            if (node.second.count(c))
                data[data.size() - 1].push_back(to_string(node.second[c]));
            else
                data[data.size() - 1].push_back("");
        }
    }
    // 构建表格
    fillTableWithHeadersAndData(MinDFATable, headers, data);

    // 构建起点和终点数据
    fillStartAndEndPoints(tabPage, QString::fromStdString(reName), MinDFAStart, MinDFAEnds);
}

/**
 * @brief Widget::fillTableWithHeadersAndData
 * 为表格控件添加数据
 * @param tableWidget 表格控件指针
 * @param headers 表头数据
 * @param data 内容数据（注意：要求每行的首位是行头，不是数据）
 */
void Widget::fillTableWithHeadersAndData(QTableWidget *tableWidget, const vector<string> &headers, const vector<vector<string>> &data)
{
    // 确定表格的行数和列数
    int rowCount = data.size();
    int columnCount = !data.empty() ? data[0].size() - 1 : 0; // 0号下标是该点的下标，作为表格行左侧的标号（横向表头），列数要少一个

    // 设置表格的行数和列数
    tableWidget->setRowCount(rowCount);
    tableWidget->setColumnCount(columnCount);

    // 将 vector<string> 转换为 QStringList 并设置表头
    QStringList headerList;
    for (const string &header : headers)
    {
        headerList.append(QString::fromStdString(header));
    }
    tableWidget->setHorizontalHeaderLabels(headerList);

    // 遍历数据，填充到表格中
    for (int row = 0; row < rowCount; ++row)
    {
        // 设置行头标签，即该点的下标（否则表格默认从1开始编号，和表格对不上）
        QTableWidgetItem *headerItem = new QTableWidgetItem(QString::fromStdString(data[row][0]));
        tableWidget->setVerticalHeaderItem(row, headerItem);
        for (int column = 0; column < columnCount; ++column)
        {
            // 创建一个新的QTableWidgetItem，并设置文本
            // 注意要后移一位
            QTableWidgetItem *item = new QTableWidgetItem(QString::fromStdString(data[row][column + 1]));

            // 将项目添加到表格中
            tableWidget->setItem(row, column, item);
        }
    }

    // 调整列宽以适应内容
    tableWidget->resizeColumnsToContents();
    tableWidget->resizeRowsToContents();

    // 设置不可修改内容
    tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
}

/**
 * @brief Widget::doInThread
 * 开启新线程阻塞执行函数逻辑
 * @param func
 */
void Widget::doInThread(function<void()> func)
{
    // 使用线程
    auto thread = new QThread();
    connect(thread, &QThread::started, [&]
            {
        // 执行函数
        func();
        // 发送执行完毕的信号
        emit Widget::finished(); });
    // 使用loop阻塞等待线程执行完毕
    QEventLoop loop;
    // 接收到完成信号时关闭loop
    connect(this, &Widget::finished, &loop, &QEventLoop::quit);
    // 启动线程
    thread->start();
    // 阻塞等待
    loop.exec();
}

/**
 * @brief Widget::fillStartAndEndPoints
 * 填充tab标签页面中的起点和终点集数据
 * @param tabPage tab标签页面指针
 * @param tabName 标签名，用于寻找对应的Lable控件
 * @param start 起点
 * @param ends 终点集
 */
void Widget::fillStartAndEndPoints(QWidget *tabPage, const QString &tabName, size_t start, set<size_t> ends)
{
    // 找到起点和终点的文本框
    QLabel *startPointLineEdit = qobject_cast<QLabel *>(tabPage->findChild<QLabel *>(tabName + "::startPointLabel"));
    QLabel *endPointLineEdit = qobject_cast<QLabel *>(tabPage->findChild<QLabel *>(tabName + "::endPointLabel"));
    startPointLineEdit->setText("start point: " + QString::fromStdString(to_string(start)));
    string endsString = "end point: ";
    for (auto c : ends)
    {
        endsString += to_string(c);
        endsString += " ";
    }
    endPointLineEdit->setText(QString::fromStdString(endsString));
}

/**
 * @brief Widget::initTable
 * 初始化LR(1)或LALR(1)的分析表和节点展示框内容
 * @param productionManager 文法管理类对象
 * @param isLA 是否时LALR(1)
 */
void Widget::initAnalyzeTableAndNodes(ProductionManager productionManager, bool isLA)
{
    lrMap = LRMap(productionManager.productions, productionManager.nameToCharacter,
                  productionManager.nameToProductions, isLA);
    auto tableWidget = isLA ? ui->LALRTableWidget : ui->LRTableWidget;
    auto nodeWidget = isLA ? ui->LALRNodeTreeWidget : ui->LRNodeTreeWidget;

    // 填充表格数据
    // 表头
    vector<string> headers;
    for (auto characher : lrMap.allChars)
    {
        headers.push_back(characher->name);
    }
    // 表格内容
    vector<vector<string>> data(lrMap.table);
    // 在最前面补充行头即可
    for (size_t i = 0; i < data.size(); i++)
    {
        data[i].insert(data[i].begin(), to_string(i));
    }
    // 填充表格
    fillTableWithHeadersAndData(tableWidget, headers, data);

    // 填充节点信息
    for (auto node : lrMap.allNodes)
    {
        // 填充节点内容
        // 每个点作为树的子节点
        auto treeNode = new QTreeWidgetItem(nodeWidget);
        treeNode->setText(0, "Node[" + QString::number(node->index) + "]");
        // 内容是孙子节点
        auto contentNode = new QTreeWidgetItem(treeNode);
        contentNode->setText(0, "content");
        // 遍历内容
        for (auto production : node->productionsInNode)
        {
            // 每个文法是内容节点的后代节点
            auto productionNode = new QTreeWidgetItem(contentNode);
            // 组装文法内容
            // 左侧
            string productionContent = "production[" + to_string(production.productionIndex) + "]   " + production.left->name + " ----> ";
            // 右侧
            for (size_t j = 0; j < production.right.size(); j++)
            {
                if (j == production.pointIndex)
                {
                    productionContent += " . "; // 下标位置
                }
                productionContent += " " + production.right[j]->name + " ";
            }
            if (production.pointIndex == production.right.size())
            {
                // 节点在最后时上面的遍历是没加上的，在这里补充
                productionContent += ".";
            }
            // expectationSet
            productionContent += "  {";
            for (auto character : production.expectationSet)
            {
                productionContent += character->name + ", ";
            }
            productionContent += " }";
            // 填充内容
            productionNode->setText(0, QString::fromStdString(productionContent));
        }

        // 填充节点出边情况
        // 出边是孙子节点
        auto edgesNode = new QTreeWidgetItem(treeNode);
        const LREdge *e = node->firstEdge;
        if (e == nullptr)
            edgesNode->setText(0, "no edges"); // 没有出边
        else
            edgesNode->setText(0, "edges");
        int eCount = 0;
        while (e != nullptr)
        {
            // 每个边是后代节点
            auto eNode = new QTreeWidgetItem(edgesNode);
            string edgeContent = "edge[" + to_string(eCount++) + "] ---- " + e->nameOnEdge + " ----> ";
            const LRNode *nextNode = lrMap.allNodes[e->endIndex];
            edgeContent += "Node[" + to_string(nextNode->index) + "]";
            e = e->nextEdge;
            // 如果有指向自己的边，就不加进来了，不然会死循环
            if (node->index == nextNode->index)
                edgeContent += " *self*";
            // 填充内容
            eNode->setText(0, QString::fromStdString(edgeContent));
        }
    }
}

/**
 * @brief Widget::toBuffs
 * 将字符串按换行符分割成列表返回
 * @param content 原始字符串
 * @return 按换行符分割后的列表结果
 */
vector<string> Widget::toBuffs(string content)
{
    vector<string> buffs;
    stringstream ss;
    ss << content;
    while (getline(ss, content, '\n'))
    {
        buffs.push_back(content);
    }
    return buffs;
}

/**
 * @brief Widget::setBtnDisabled
 * 设置具有执行功能的按钮为不可用状态
 */
void Widget::setBtnDisabled()
{
    ui->runGenFABtn->setEnabled(false);
    ui->runGenCodeBtn->setEnabled(false);

    ui->saveCodeBtn->setEnabled(false);

    ui->runGenExeBtn->setEnabled(false);
    ui->runExeBtn->setEnabled(false);

    ui->runProdBtn->setEnabled(false);
    ui->runAnalyzeBtn->setEnabled(false);
}

Widget::~Widget()
{
    delete ui;
}

/**
 * @brief Widget::on_openReBtn_clicked
 * 点击按钮选择正则表达式文件
 */
void Widget::on_openReBtn_clicked()
{
    // 选择文件
    QString filePath = openFile("选择一个正则表达式文件", "Regex File (*.regex)");
    QString content = readFile(filePath);
    // 展示到文本框中
    ui->reTextEdit->setText(content);
}

/**
 * @brief Widget::showLoading
 * 展示加载界面
 * @param title 加载弹框标题
 * @param info 弹框内的提示信息
 */
void Widget::showLoading(QString title, QString info)
{
    loadingDialog->setWindowTitle(title);
    loadingDialog->setLabelText(info);
    loadingDialog->setMaximum(0);
    loadingDialog->setMinimum(0);
    loadingDialog->show();
}

/**
 * @brief Widget::closeLoading
 * 关闭加载弹框
 */
void Widget::closeLoading()
{
    loadingDialog->accept();
}

/**
 * @brief Widget::on_runGenFABtn_clicked
 * 点击按钮生成正则的NFA、DFA、最小化DFA
 */
void Widget::on_runGenFABtn_clicked()
{
    // 获取文本框内容，构造ReManager
    string content = ui->reTextEdit->toPlainText().toStdString();
    vector<string> buffs = toBuffs(content);
    auto reManager = ReManager(buffs);
    // 构建自动机，和原来的逻辑不同，因为要打印每个自动机的数据，改为一个个构建
    // 将GUI的成员变量通过引用传入，同步内部数据
    auto faBuilder = FABuilder(
        reManager.reMap,
        symbols,
        NFAStart,
        DFAStart,
        MinDFAStart,
        NFAEnds,
        DFAEnds,
        MinDFAEnds,
        NFANodes,
        DFANodes,
        MinDFANodes,
        DFAsAndEndsOfTokenType,
        DFAsAndEndsOfAuxiliary,
        reManager.reOfTokenType,
        reManager.reForAuxiliary);
    // 先清空原来的内容
    clearTab();
    // 设置加载框，这里要更新进度条进度，所以手动show
    loadingDialog->setWindowTitle("生成自动机中");
    loadingDialog->setRange(0, reManager.reOfTokenType.size() + reManager.reForAuxiliary.size()); // 总进度和正则表达式个数对应
    loadingDialog->show();
    // 遍历所有正则，逐个构建自动机
    size_t i = 0;
    for (const auto &res : reManager.reMap)
    {
        auto reName = res.first;
        // 设置加载框提示信息
        loadingDialog->setLabelText(QString::fromStdString("正在生成 " + reName + " 的自动机"));
        doInThread([&]
                   {
            // 生成自动机
            faBuilder.buildDFAs(reName); });
        // 更新进度条
        loadingDialog->setValue(++i);
        // 添加表格和起点终点信息
        initTab(reName);
    }
    // 保存所有最小化DFA信息
    DFAsAndEndsOfTokenType = faBuilder.DFAsAndEndsOfTokenType;
    DFAsAndEndsOfAuxiliary = faBuilder.DFAsAndEndsOfAuxiliary;
    // 设置生成分词程序按钮可用
    ui->runGenCodeBtn->setEnabled(true);
    // 将页面调到NFA展示界面
    ui->tabWidget_2->setCurrentIndex(0);

    // 执行结束，关闭对话框
    closeLoading();
}

/**
 * @brief Widget::on_reTextEdit_textChanged
 * 如果文本不为空则启用生成自动机的按钮，反之禁用该按钮和生成分词程序的按钮
 */
void Widget::on_reTextEdit_textChanged()
{
    QString content = ui->reTextEdit->toPlainText();
    if (content != "")
        ui->runGenFABtn->setEnabled(true);
    else
    {
        ui->runGenFABtn->setEnabled(false);
        ui->runGenCodeBtn->setEnabled(false);
    }
}

/**
 * @brief Widget::on_runGenCodeBtn_clicked
 * 点击生成分词程序代码
 */
void Widget::on_runGenCodeBtn_clicked()
{
    auto codeAnalyzer = CodeAnalyzer(DFAsAndEndsOfTokenType, DFAsAndEndsOfAuxiliary);
    // 生成cpp文件
    codeAnalyzer.generateCpp();
    // 打开并展示cpp文件内容
    QString fileName = QString::fromStdString(FILE_NAME) + ".cpp";
    QFile file(fileName);
    if (file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QTextStream in(&file);
        // 展示内容
        ui->codeBrowser->setPlainText(in.readAll());
        file.close();
    }
    else
    {
        // 如果文件无法打开，显示错误信息
        ui->codeBrowser->setPlainText("无法打开文件: " + fileName);
    }
    // 将页面调到代码显示界面
    ui->tabWidget_2->setCurrentIndex(ui->tabWidget_2->count() - 1);
    // 设置保存文件按钮可用
    ui->saveCodeBtn->setEnabled(true);
}

/**
 * @brief Widget::on_saveCodeBtn_clicked
 * 保存cpp文件到本地
 */
void Widget::on_saveCodeBtn_clicked()
{
    QString content = ui->codeBrowser->toPlainText();
    saveFile(content, ".cpp");
}

/**
 * @brief Widget::on_openCodeBtn_clicked
 * 选择分词程序cpp文件并展示
 */
void Widget::on_openCodeBtn_clicked()
{
    // 读取文件
    QString filePath = openFile("选择分词程序cpp文件", "c++程序源文件 (*.cpp)");
    // 保存选择的路径
    codeFilePath = filePath;
    QString content = readFile(filePath);
    // 展示内容到文本框
    ui->codeTextEdit->setText(content);
}

/**
 * @brief Widget::on_codeTextEdit_textChanged
 * 如果文本不为空则启用生成可执行程序的按钮，反之禁用按钮
 */
void Widget::on_codeTextEdit_textChanged()
{
    QString content = ui->codeTextEdit->toPlainText();
    if (content != "")
        ui->runGenExeBtn->setEnabled(true);
    else
        ui->runGenExeBtn->setEnabled(false);
}

/**
 * @brief Widget::on_runGenExeBtn_clicked
 * 生成分词程序对应的可执行程序
 */
void Widget::on_runGenExeBtn_clicked()
{
    // 显示加载框
    showLoading("请稍候", "生成可执行程序中...");

    // 使用多线程执行
    doInThread([&]
               { CodeAnalyzer::generateExe(string(codeFilePath.toLocal8Bit())); });

    // 执行结束，关闭对话框
    closeLoading();

    // 显示提示框
    QMessageBox::information(this, "生成exe程序成功", "可执行程序已于当地目录下生成，程序文件名为" + QString::fromStdString(FILE_NAME) + ".exe（后续将使用该程序进行代码词法分析）");
    // 设置执行生成标志为true
    isExeGen = true;
    // 触发待分析代码展示文本框的文本内容改变槽函数，这样如果用户先选好了要分析的代码内容才来生成exe的情况下也能让分析按钮可用
    on_sourceTextEdit_textChanged();
}

/**
 * @brief Widget::on_sourceTextEdit_textChanged
 * 如果文本不为空且exe生成标志为true则启用生成可执行程序的按钮，反之禁用按钮
 */
void Widget::on_sourceTextEdit_textChanged()
{
    QString content = ui->sourceTextEdit->toPlainText();
    if (content != "" && isExeGen)
        ui->runExeBtn->setEnabled(true);
    else
        ui->runExeBtn->setEnabled(false);
}

/**
 * @brief Widget::on_openSourceBtn_clicked
 * 选择待分析的代码文件
 */
void Widget::on_openSourceBtn_clicked()
{
    // 读取文件
    QString filePath = openFile("选择待分析的代码文件", "程序源文件 (*)");
    QString content = readFile(filePath);
    // 展示内容到文本框
    ui->sourceTextEdit->setText(content);
}

/**
 * @brief Widget::on_runExeBtn_clicked
 * 执行分词程序
 */
void Widget::on_runExeBtn_clicked()
{
    // 显示加载对话框
    showLoading("请稍候", "执行分词程序中...");

    // 获取待分析的文件内容
    QString content = ui->sourceTextEdit->toPlainText();
    // 保存到本地（为了获取到路径）
    QString path = "./" + QString::fromStdString(FILE_NAME) + ".txt";
    QFile file(path);
    file.open(QFile::WriteOnly | QFile::Truncate);
    QTextStream out(&file);
    out << content;
    file.close();

    // 执行程序
    doInThread([&]
               { CodeAnalyzer::analyze(string(path.toLocal8Bit())); });

    // 结果在tokens.txt，读取并展示数据
    QString tokens = readFile("./tokens.txt");
    QStringList lines = tokens.split('\n');
    // 使用表格的方式展示
    // 表头
    vector<string> headers;
    headers.push_back("tokenType");
    headers.push_back("tokenValue");
    // 表格内容
    vector<vector<string>> data;
    for (int i = 0; i < lines.size(); i++)
    {
        string line = lines[i].toStdString();
        if (line.empty())
            continue;
        size_t pos = line.find(" ");
        if (pos == string::npos)
            continue;
        string tokenType = line.substr(0, pos);
        string tokenValue = line.substr(pos + 1);
        data.emplace_back();
        // 行头从1开始即可
        data[data.size() - 1].push_back(to_string(i + 1));
        data[data.size() - 1].push_back(tokenType);
        data[data.size() - 1].push_back(tokenValue);
    }
    // 填充内容
    fillTableWithHeadersAndData(ui->tokenTableWidget, headers, data);

    // 关闭加载对话框
    closeLoading();
}

/**
 * @brief Widget::on_saveResultBtn_clicked
 * 保存分词结果
 */
void Widget::on_saveResultBtn_clicked()
{
    // 获取分词结果内容
    QString result;
    int rowCount = ui->tokenTableWidget->rowCount();
    int columnCount = ui->tokenTableWidget->columnCount();
    for (int row = 0; row < rowCount; ++row)
    {
        for (int column = 0; column < columnCount; ++column)
        {
            QTableWidgetItem *item = ui->tokenTableWidget->item(row, column);
            if (item)
            {
                result += item->text();
            }
            if (column == 0)
                result += " "; // 列之间的分隔符
        }
        result += "\n"; // 行之间的分隔符
    }
    // 保存内容
    saveFile(result, ".token");
}

/**
 * @brief Widget::on_openProdBtn_clicked
 * 选择文法语句文件
 */
void Widget::on_openProdBtn_clicked()
{
    // 读取文件
    QString filePath = openFile("选择文法语句文件", "Prod File (*.prod)");
    QString content = readFile(filePath);
    // 展示内容到文本框
    ui->prodTextEdit->setText(content);
}

/**
 * @brief Widget::on_prodTextEdit_textChanged
 * 如果文法内容和建点类型标记内容不为空则启用初始化文法语句并建立分析表的按钮，反之禁用按钮
 * 与on_prodBuildTypeTextEdit_textChanged函数逻辑相同
 */
void Widget::on_prodTextEdit_textChanged()
{
    QString prodContent = ui->prodTextEdit->toPlainText();
    QString buildTypeContent = ui->prodBuildTypeTextEdit->toPlainText();
    if (prodContent != "" && buildTypeContent != "")
        ui->runProdBtn->setEnabled(true);
    else
        ui->runProdBtn->setEnabled(false);
}

/**
 * @brief Widget::on_prodBuildTypeTextEdit_textChanged
 * 如果文法内容和建点类型标记内容不为空则启用初始化文法语句并建立分析表的按钮，反之禁用按钮
 * 与on_prodTextEdit_textChanged函数逻辑相同
 */
void Widget::on_prodBuildTypeTextEdit_textChanged()
{
    QString prodContent = ui->prodTextEdit->toPlainText();
    QString buildTypeContent = ui->prodBuildTypeTextEdit->toPlainText();
    if (prodContent != "" && buildTypeContent != "")
        ui->runProdBtn->setEnabled(true);
    else
        ui->runProdBtn->setEnabled(false);
}

/**
 * @brief Widget::on_runProdBtn_clicked
 * 初始化文法语句，计算出非终结符的first和follow集合，生成LR(1)和LALR(1)分析表，展示结果
 */
void Widget::on_runProdBtn_clicked()
{
    // 显示加载框
    showLoading("请稍候", "初始化文法语句中...");

    // 多线程执行
    ProductionManager productionManager;
    doInThread([&]
               {
        // 获取文法语句内容
        QString prodContent = ui->prodTextEdit->toPlainText();
        QString buildTypeContent = ui->prodBuildTypeTextEdit->toPlainText();
        // 初始化文法语句
        auto productionBuffs = toBuffs(prodContent.toStdString());
        auto prodBuildTypeBuffs = toBuffs(buildTypeContent.toStdString());
        productionManager = ProductionManager(productionBuffs, prodBuildTypeBuffs); });

    // 获取first和follow集合并展示
    // 清空原本的内容
    ui->firstTextBrowser->clear();
    ui->followTextBrowser->clear();
    for (auto nameToC : productionManager.nameToCharacter)
    {
        string name = nameToC.first;
        auto character = nameToC.second;
        if (character->isEnd)
            continue; // 终结符跳过
        string firstInfo = "First(" + name + ") = {";
        string followInfo = "Follow(" + name + ") = {";
        for (auto fir : character->firstSet)
        {
            firstInfo += fir->name;
            firstInfo += ", ";
        }
        for (auto fol : character->followSet)
        {
            followInfo += fol->name;
            followInfo += ", ";
        }
        firstInfo += "}\n";
        followInfo += "}\n";
        // 展示
        ui->firstTextBrowser->append(QString::fromStdString(firstInfo));
        ui->followTextBrowser->append(QString::fromStdString(followInfo));
    }

    // 建立LR(1)/LALR(1)分析表，操作基本一样，提取为函数
    initAnalyzeTableAndNodes(productionManager, false);
    initAnalyzeTableAndNodes(productionManager, true);

    // 将页面调到文法解析显示界面
    ui->tabWidget_3->setCurrentIndex(0);

    // 标记已经执行文法解析逻辑
    isProdGen = true;
    // 触发分词结果展示文本框的文本内容改变槽函数，这样如果用户先选好了要分词结果才来解析文法的情况下也能让分析语法按钮可用
    on_tokenTextEdit_textChanged();

    // 关闭加载框
    closeLoading();
}

/**
 * @brief Widget::on_openTokenBtn_clicked
 * 点击选择分词结果文件
 */
void Widget::on_openTokenBtn_clicked()
{
    // 读取文件
    QString filePath = openFile("选择分词结果文件", "Token File (*.token)");
    QString content = readFile(filePath);
    // 展示内容到文本框
    ui->tokenTextEdit->setText(content);
}

/**
 * @brief Widget::on_tokenTextEdit_textChanged
 * 如果文本不为空且执行文法解析逻辑的标志为true则启用执行语法分析的按钮，反之禁用按钮
 */
void Widget::on_tokenTextEdit_textChanged()
{
    QString content = ui->tokenTextEdit->toPlainText();
    if (content != "" && isProdGen)
        ui->runAnalyzeBtn->setEnabled(true);
    else
        ui->runAnalyzeBtn->setEnabled(false);
}

/**
 * @brief Widget::on_runAnalyzeBtn_clicked
 * 点击按钮执行语法分析逻辑
 */
void Widget::on_runAnalyzeBtn_clicked()
{
    // 显示加载框
    showLoading("请稍候", "分析代码语法中...");

    // 初始化token数据
    QString content = ui->tokenTextEdit->toPlainText();
    vector<string> buffs = toBuffs(content.toStdString());
    const auto tokenManager = TokenManager(buffs);
    // 初始化解析类
    stringstream out; // 用于记录日志内容
    GNode root;       // 根节点

    // 使用多线程
    doInThread([&]
               {
        auto parser = Parser(lrMap.productions, lrMap.tableMap, lrMap.allEndCharNames, tokenManager.tokens, out);
        // 语法分析
        root = parser.parse(); });

    // 展示日志内容
    ui->analyzeTextBrowser->setText(QString::fromStdString(out.str()));
    // 展示语法树
    // 清空原来的数据
    ui->treeWidget->clear();
    // 单独建一个空item，方便递归（因为ui->treeWidget是widget不是item）
    auto rootFather = new QTreeWidgetItem(ui->treeWidget);
    rootFather->setText(0, "Syntax Tree");
    buildGTree(root, rootFather);
    // 全部展开
    ui->treeWidget->expandAll();
    // 跳转到分析过程界面
    ui->tabWidget_5->setCurrentIndex(0);

    // 关闭加载框
    closeLoading();
}

/**
 * @brief Widget::on_openBuildTypeBtn_clicked
 * 点击按钮选择建点标记文件
 */
void Widget::on_openBuildTypeBtn_clicked()
{
    // 读取文件
    QString filePath = openFile("选择建点标记文件", "ProdBuildType File (*.pbt)");
    QString content = readFile(filePath);
    // 展示内容到文本框
    ui->prodBuildTypeTextEdit->setText(content);
}
